
/**
\file
Definition of the rainbow::graphics_device class.
\author Sean Tromans
*/

#include <sun_pre_compiled.hpp>
#include <rainbow/graphics_device/graphics_device.hpp>

#if !defined(SUN_DISABLE_LUA)
#include <sun/lua/lua.hpp>
#endif

namespace rainbow
{

	namespace detail
	{

		/** Functor to find a state in an associative container. */
		template < typename handle_t, typename state_t >
		struct find_state
		{
			find_state( state_t const & s )
				:	state( s )
			{}

			bool operator( )( std::pair< handle_t, state_t > const & pair ) const
			{
				return pair.second == state;
			}

			state_t state;
		};

	}

	//
	//	Public Member Function
	//

	/**
	Default Constructor
	\author Sean Tromans
	*/
	graphics_device::graphics_device( )
		:		m_current_id(1)

			,	m_current_blend_state(1)
			,	m_current_depth_state(1)
			,	m_current_stencil_state(1)
			,	m_current_culling_state(1)
	{
		m_blend_states.insert( std::make_pair( 1, blend_state( ) ) );
		m_depth_states.insert( std::make_pair( 1, depth_state( ) ) );
		m_stencil_states.insert( std::make_pair( 1, stencil_state( ) ) );
		m_culling_states.insert( std::make_pair( 1, culling_state( ) ) );
	}

	/**
	Adds a new blend_state to the graphics_device.
	@param new_state The state to register with the device.
	@return A handle to a new blend_state unless there is already a blend_state matching new_state, in which case a handle to the old state will be returned.
	\author Sean Tromans
	*/
	graphics_device::blend_handle graphics_device::register_state( blend_state const & new_state )
	{
		auto const iter = std::find_if( m_blend_states.begin( ), m_blend_states.end( ), detail::find_state< blend_handle, blend_state >( new_state ) );
		if ( iter != m_blend_states.end( ) )
		{
			return iter->first;
		}

		blend_handle const handle( ++m_current_id );
		NMN_VERIFY( m_blend_states.insert( std::make_pair( handle, new_state ) ).second );
		return handle;
	}

	/**
	Gets a reference to a blend_state from the graphics_device.
	@param handle The blend_handle to the blend_state required.
	@return A refernce to a blend_state.
	\throws state_not_found
	\author Sean Tromans
	*/
	blend_state & graphics_device::get_blend_state( graphics_device::blend_handle const & handle )
	{
		auto const iter = m_blend_states.find( handle );
		if ( iter == m_blend_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		return iter->second;
	}

	/**
	Sets the currently active blend_state.
	@param handle The handle to the blend_state to activate.
	\throws state_not_found
	\author Sean Tromans
	*/
	void graphics_device::activate_blend_state( graphics_device::blend_handle const & handle )
	{
		if ( m_current_blend_state == handle )
		{
			return;
		}

		auto const iter = m_blend_states.find( handle );
		if ( iter == m_blend_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		iter->second.apply( get_blend_state( active_blend_state( ) ) );
		m_current_blend_state = iter->first;
	}

	/**
	Adds a new depth_state to the graphics_device.
	@param new_state The state to register with the device.
	@return A handle to a new depth_state unless there is already a depth_state matching new_state, in which case a handle to the old state will be returned.
	\author Sean Tromans
	*/
	graphics_device::depth_handle graphics_device::register_state( depth_state const & new_state )
	{
		auto const iter = std::find_if( m_depth_states.begin( ), m_depth_states.end( ), detail::find_state< depth_handle, depth_state >( new_state ) );
		if ( iter != m_depth_states.end( ) )
		{
			return iter->first;
		}

		depth_handle const handle( ++m_current_id );
		NMN_VERIFY( m_depth_states.insert( std::make_pair( handle, new_state ) ).second );
		return handle;
	}

	/**
	Gets a reference to a depth_state from the graphics_device.
	@param handle The depth_handle to the depth_state required.
	@return A refernce to a depth_state.
	\throws state_not_found
	\author Sean Tromans
	*/
	depth_state & graphics_device::get_depth_state( graphics_device::depth_handle const & handle )
	{
		auto const iter = m_depth_states.find( handle );
		if ( iter == m_depth_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		return iter->second;
	}

	/**
	Sets the currently active depth_state.
	@param handle The handle to the depth_state to activate.
	\throws state_not_found
	\author Sean Tromans
	*/
	void graphics_device::activate_depth_state( graphics_device::depth_handle const & handle )
	{
		if ( m_current_depth_state == handle )
		{
			return;
		}

		auto const iter = m_depth_states.find( handle );
		if ( iter == m_depth_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		iter->second.apply( get_depth_state( active_depth_state( ) ) );
		m_current_depth_state = iter->first;
	}

	/**
	Adds a new stencil_state to the graphics_device.
	@param new_state The state to register with the device.
	@return A handle to a new stencil_state unless there is already a stencil_state matching new_state, in which case a handle to the old state will be returned.
	\author Sean Tromans
	*/
	graphics_device::stencil_handle graphics_device::register_state( stencil_state const & new_state )
	{
		auto const iter = std::find_if( m_stencil_states.begin( ), m_stencil_states.end( ), detail::find_state< stencil_handle, stencil_state >( new_state ) );
		if ( iter != m_stencil_states.end( ) )
		{
			return iter->first;
		}

		stencil_handle const handle( ++m_current_id );
		NMN_VERIFY( m_stencil_states.insert( std::make_pair( handle, new_state ) ).second );
		return handle;
	}

	/**
	Gets a reference to a stencil_state from the graphics_device.
	@param handle The stencil_handle to the stencil_state required.
	@return A refernce to a stencil_state.
	\throws state_not_found
	\author Sean Tromans
	*/
	stencil_state & graphics_device::get_stencil_state( graphics_device::stencil_handle const & handle )
	{
		auto const iter = m_stencil_states.find( handle );
		if ( iter == m_stencil_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		return iter->second;
	}

	/**
	Sets the currently active stencil_state.
	@param handle The handle to the stencil_state to activate.
	\throws state_not_found
	\author Sean Tromans
	*/
	void graphics_device::activate_stencil_state( graphics_device::stencil_handle const & handle )
	{
		if ( m_current_stencil_state == handle )
		{
			return;
		}

		auto const iter = m_stencil_states.find( handle );
		if ( iter == m_stencil_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		iter->second.apply( get_stencil_state( active_stencil_state( ) ) );
		m_current_stencil_state = iter->first;
	}

	/**
	Adds a new culling_state to the graphics_device.
	@param new_state The state to register with the device.
	@return A handle to a new culling_state unless there is already a culling_state matching new_state, in which case a handle to the old state will be returned.
	\author Sean Tromans
	*/
	graphics_device::culling_handle graphics_device::register_state( culling_state const & new_state )
	{
		auto const iter = std::find_if( m_culling_states.begin( ), m_culling_states.end( ), detail::find_state< culling_handle, culling_state >( new_state ) );
		if ( iter != m_culling_states.end( ) )
		{
			return iter->first;
		}

		culling_handle const handle( ++m_current_id );
		NMN_VERIFY( m_culling_states.insert( std::make_pair( handle, new_state ) ).second );
		return handle;
	}

	/**
	Gets a reference to a culling_state from the graphics_device.
	@param handle The culling_handle to the culling_state required.
	@return A refernce to a culling_state.
	\throws state_not_found
	\author Sean Tromans
	*/
	culling_state & graphics_device::get_culling_state( graphics_device::culling_handle const & handle )
	{
		auto const iter = m_culling_states.find( handle );
		if ( iter == m_culling_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		return iter->second;
	}

	/**
	Sets the currently active culling_state.
	@param handle The handle to the culling_state to activate.
	\throws state_not_found
	\author Sean Tromans
	*/
	void graphics_device::activate_culling_state( graphics_device::culling_handle const & handle )
	{
		if ( m_current_culling_state == handle )
		{
			return;
		}

		auto const iter = m_culling_states.find( handle );
		if ( iter == m_culling_states.end( ) )
		{
			throw state_not_found( __FUNCTION__, handle );
		}

		iter->second.apply( get_culling_state( active_culling_state( ) ) );
		m_current_culling_state = iter->first;
	}

	//
	//	Static Member Functions
	//

	/**
	Exposes the graphics_device to lua.
	@param L The Lua State being initialised.
	\author Sean Tromans.
	*/
	void graphics_device::lua_interface( lua_State * L )
	{
#if !defined(SUN_DISABLE_LUA)
		blend_handle (graphics_device::*register_blend_state)( blend_state const & ) = &graphics_device::register_state;
		depth_handle (graphics_device::*register_depth_state)( depth_state const & ) = &graphics_device::register_state;
		stencil_handle (graphics_device::*register_stencil_state)( stencil_state const & ) = &graphics_device::register_state;
		culling_handle (graphics_device::*register_culling_state)( culling_state const & ) = &graphics_device::register_state;

		luabind::module( L, "rainbow" )
		[
			luabind::class_< graphics_device >( "graphics_device" )
				.def( "register_state", register_blend_state )
				.def( "register_state", register_depth_state )
				.def( "register_state", register_stencil_state )
				.def( "register_state", register_culling_state )

				.def( "get_blend_state", &graphics_device::get_blend_state )
				.def( "get_depth_state", &graphics_device::get_depth_state )
				.def( "get_stencil_state", &graphics_device::get_stencil_state )
				.def( "get_culling_state", &graphics_device::get_culling_state )

				.def( "active_blend_state", &graphics_device::active_blend_state )
				.def( "active_depth_state", &graphics_device::active_depth_state )
				.def( "active_stencil_state", &graphics_device::active_stencil_state )
				.def( "active_culling_state", &graphics_device::active_culling_state )

				.def( "activate_blend_state", &graphics_device::activate_blend_state )
				.def( "activate_depth_state", &graphics_device::activate_depth_state )
				.def( "activate_stencil_state", &graphics_device::activate_stencil_state )
				.def( "activate_culling_state", &graphics_device::activate_culling_state )

				.property( "blend_state", &graphics_device::active_blend_state, &graphics_device::activate_blend_state )
				.property( "depth_state", &graphics_device::active_depth_state, &graphics_device::activate_depth_state )
				.property( "stencil_state", &graphics_device::active_stencil_state, &graphics_device::activate_stencil_state )
				.property( "culling_state", &graphics_device::active_culling_state, &graphics_device::activate_culling_state )
		];
#else
		NMN_UNUSED( L );
#endif
	}

}
